package com.test.mvcframework.ioc;

import com.test.mvcframework.annotations.*;
import com.test.mvcframework.interceptor.HandlerInterceptor;
import com.test.mvcframework.pojo.handler.*;
import com.test.mvcframework.pojo.view.TemplateViewResolver;
import com.test.mvcframework.utils.ReflectionUtils;
import org.omg.PortableInterceptor.Interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

/**
 * 容器
 *
 * @author felix
 */
public class ApplicationContext implements BeanFactory {
    /**
     * ioc bean
     */
    private Map<String, Object> singletons = new ConcurrentHashMap<>();

    /**
     * 工厂bean
     */
    private Map<Class<?>, FactoryBean> factoryBeans = new ConcurrentHashMap<>();

    public void init(String basePackage) throws Exception {
        // init bean factory
        Set<Class<?>> factoryBeanClasses =
                ReflectionUtils.getInterfaceClasses(basePackage, FactoryBean.class);
        factoryBeanClasses.forEach(factoryBeanClass -> {
            try {
                FactoryBean factoryBean = (FactoryBean) factoryBeanClass.newInstance();
                factoryBeans.put(factoryBean.getType(), factoryBean);
            } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
        });

        Set<Class<?>> componentClasses =
                ReflectionUtils.getTypeAnnotatedClasses(basePackage, Component.class);
        Set<Class<?>> serviceClasses =
                ReflectionUtils.getTypeAnnotatedClasses(basePackage, Service.class);
        Set<Class<?>> iocClasses = new HashSet<>(componentClasses.size() + serviceClasses.size());
        iocClasses.addAll(componentClasses);
        iocClasses.addAll(serviceClasses);
        for (Class<?> iocClass : iocClasses) {
            Component component = iocClass.getAnnotation(Component.class);
            if (component == null) {
                Service service = iocClass.getAnnotation(Service.class);
                if (service != null) {
                    component = ReflectionUtils.convertAnnotationType(service, Component.class);
                }
            }
            assert component != null;

            FactoryBean factoryBean = factoryBeans.get(iocClass);
            Object instance = null;
            if (factoryBean != null) {
                instance = factoryBean.getObject();
            } else {
                instance = iocClass.newInstance();
            }

            String beanName = component.value();
            singletons.put(beanName, instance);
        }

        Set<Class<?>> controllerClasses =
                ReflectionUtils.getTypeAnnotatedClasses(basePackage, Controller.class);

        Map<String, HandlerInterceptor> interceptorMap = getBeansOfType(HandlerInterceptor.class);
        HandlerInterceptor[] interceptors = new HandlerInterceptor[interceptorMap.size()];
        for (int i = 0; i < interceptorMap.entrySet().size(); i++) {
            interceptors[i] = interceptorMap.entrySet().iterator().next().getValue();
        }

        for (Class<?> aClass : controllerClasses) {
            RequestMapping requestMapping = aClass.getAnnotation(RequestMapping.class);
            String outerPath = requestMapping.value();

            Object instance = aClass.newInstance();
            for (Method method : aClass.getMethods()) {
                RequestMapping methodRequestMapping =
                        method.getDeclaredAnnotation(RequestMapping.class);
                if (methodRequestMapping == null) {
                    continue;
                }
                String innerPath = methodRequestMapping.value();
                String fullPath = outerPath + innerPath;

                Map<String, Integer> paramsIndexMap = new HashMap<>();
                for (int i = 0; i < method.getParameters().length; i++) {
                    Class<?> klass = method.getParameterTypes()[i];
                    String paramName = null;
                    if (klass == HttpServletRequest.class) {
                        paramName = HttpServletRequest.class.getName();
                    } else if (klass == HttpServletResponse.class) {
                        paramName = HttpServletResponse.class.getName();
                    } else {
                        paramName = method.getParameters()[i].getName();
                    }
                    paramsIndexMap.put(paramName, i);
                }

                Handler handler = new MethodHandler(instance, method, paramsIndexMap);

                HandlerExecutionChain executionChain =
                        new HandlerExecutionChain(handler, interceptors);
                HandlerMapping handlerMapping =
                        new DefaultHandlerMapping(executionChain, Pattern.compile(fullPath));
                String beanName = "handlerMapping#" + UUID.randomUUID();
                singletons.put(beanName, handlerMapping);

                singletons.put("methodHandlerAdapter", new MethodHandlerAdapter());
                singletons.put("templateViewResolver", new TemplateViewResolver());
            }

            for (Field field : aClass.getDeclaredFields()) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    Class fieldClass = field.getType();
                    Object value = getBean(fieldClass);
                    field.setAccessible(true);
                    field.set(instance, value);
                }
            }
        }

    }

    @Override
    public Object getBean(String beanName) {
        return singletons.get(beanName);
    }

    @Override
    public <T> T getBean(Class<T> tClass) {
        for (Map.Entry<String, Object> entry : singletons.entrySet()) {
            if (tClass.isAssignableFrom(entry.getValue().getClass())) {
                return (T) entry.getValue();
            }
        }
        return null;
    }

    @Override
    public <T> Map<String, T> getBeansOfType(Class<T> type) {
        Map<String, T> beanMap = new HashMap<>();
        for (Map.Entry<String, Object> entry : singletons.entrySet()) {
            if (type.isAssignableFrom(entry.getValue().getClass())) {
                beanMap.put(entry.getKey(), (T) entry.getValue());
            }
        }
        return beanMap;
    }
}
